<?php

declare(strict_types=1);
/**
 * This file is part of MoChat.
 * @link     https://mo.chat
 * @document https://mochat.wiki
 * @contact  group@mo.chat
 * @license  https://github.com/mochat-cloud/mochat/blob/master/LICENSE
 */
namespace MoChat\App\WorkAgent\Action\Callback;

use EasyWeChat\Factory;
use Hyperf\Di\Annotation\Inject;
use Hyperf\HttpServer\Annotation\Controller;
use Hyperf\HttpServer\Annotation\RequestMapping;
use Hyperf\Logger\LoggerFactory;
use Hyperf\Utils\Codec\Json;
use Hyperf\Utils\Context;
use MoChat\App\Common\Exception\CommonException;
use MoChat\App\Corp\Contract\CorpContract;
use MoChat\App\WorkAgent\Event\CancelAuthRawEvent;
use MoChat\App\WorkAgent\Event\ChangeAuthRawEvent;
use MoChat\App\WorkAgent\Event\CreateAuthRawEvent;
use MoChat\App\WorkAgent\Event\ResetPermanentCodeRawEvent;
use MoChat\App\WorkContact\Event\AddContactRawEvent;
use MoChat\App\WorkContact\Event\AddHalfContactRawEvent;
use MoChat\App\WorkContact\Event\DeleteContactRawEvent;
use MoChat\App\WorkContact\Event\Employee\DeleteFollowEmployeeRawEvent;
use MoChat\App\WorkContact\Event\Tag\CreateTagRawEvent;
use MoChat\App\WorkContact\Event\Tag\DeleteTagRawEvent;
use MoChat\App\WorkContact\Event\Tag\ShuffleTagRawEvent;
use MoChat\App\WorkContact\Event\Tag\UpdateContactTagRawEvent;
use MoChat\App\WorkContact\Event\Tag\UpdateTagRawEvent;
use MoChat\App\WorkContact\Event\TransferFailRawEvent;
use MoChat\App\WorkContact\Event\UpdateContactRawEvent;
use MoChat\App\WorkDepartment\Event\CreateDepartmentRawEvent;
use MoChat\App\WorkDepartment\Event\DeleteDepartmentRawEvent;
use MoChat\App\WorkDepartment\Event\UpdateDepartmentRawEvent;
use MoChat\App\WorkEmployee\Event\CreateEmployeeRawEvent;
use MoChat\App\WorkEmployee\Event\DeleteEmployeeRawEvent;
use MoChat\App\WorkEmployee\Event\UpdateEmployeeRawEvent;
use MoChat\App\WorkMessage\Event\MessageArchiveRawEvent;
use MoChat\App\WorkMessage\Event\MessageNotifyRawEvent;
use MoChat\App\WorkRoom\Event\CreateRoomRawEvent;
use MoChat\App\WorkRoom\Event\DismissRoomRawEvent;
use MoChat\App\WorkRoom\Event\UpdateRoomRawEvent;
use MoChat\Framework\Action\AbstractAction;
use MoChat\Framework\Constants\ErrorCode;
use Psr\EventDispatcher\EventDispatcherInterface;
use Psr\Http\Message\ServerRequestInterface;

/**
 * @Controller
 */
class AppTemplate extends AbstractAction
{
    /**
     * @var \Psr\Log\LoggerInterface
     */
    private $logger;

    /**
     * @Inject
     * @var CorpContract
     */
    private $corpService;

    public function __construct(LoggerFactory $loggerFactory)
    {
        $this->logger = $loggerFactory->get(__CLASS__);
    }

    /**
     * @RequestMapping(path="/callback/agent/appTemplate", methods="GET,POST")
     */
    public function handle()
    {
        try {
            $config = $this->getConfig();
            $this->reloadRequest($config);

            $workApp = Factory::openWork($config);
            /** @var \EasyWeChat\OpenWork\Application $workApp */
            $workApp = rebind_app($workApp, $this->request);
            $params = $this->request->all();
            $decParams = $workApp->server->getMessage();
            $this->logger->info(sprintf('代开发模板微信回调接收成功，加密信息[%s]，解密信息[%s]', Json::encode($params), Json::encode($decParams)));

            $workApp->server->push([$this, 'messageHandler']);
            $response = $workApp->server->serve();
            return $response->getContent();
        } catch (\Exception $e) {
            $this->logger->error(sprintf('代开发模板微信回调失败，错误信息[%s]', $e->getMessage()));
        }
        return 'error';
    }

    public function messageHandler(array $message)
    {
        try {
            $infoTye = $message['InfoType'] ?? '';
            if (array_key_exists('ToUserName', $message) && ! empty($message['ToUserName'])) {
                $infoTye = $message['Event'] ?? '';
            }

            $changeType = $message['ChangeType'] ?? 'default';
            if (empty($infoTye)) {
                return 'success';
            }

            $events = $this->getEvents();
            if (isset($events[$infoTye . '.' . $changeType])) {
                $className = $events[$infoTye . '.' . $changeType];
                $message = $this->transformMessage($message);
                go(function () use ($className, $message) {
                    $eventDispatcher = make(EventDispatcherInterface::class);
                    $eventDispatcher->dispatch(new $className($message));
                });
            }
        } catch (\Throwable $e) {
            $this->logger->error(sprintf('代开发模板微信回调失败，错误信息[%s]', $e->getMessage()));
        }

        return 'success';
    }

    public function getEvents()
    {
        return [
            'create_auth.default' => CreateAuthRawEvent::class,
            'change_auth.default' => ChangeAuthRawEvent::class,
            'cancel_auth.default' => CancelAuthRawEvent::class,
            'reset_permanent_code.default' => ResetPermanentCodeRawEvent::class,
            'change_external_contact.add_external_contact' => AddContactRawEvent::class,
            'change_external_contact.edit_external_contact' => UpdateContactRawEvent::class,
            'change_external_contact.add_half_external_contact' => AddHalfContactRawEvent::class,
            'change_external_contact.del_external_contact' => DeleteContactRawEvent::class,
            'change_external_contact.del_follow_user' => DeleteFollowEmployeeRawEvent::class,
            'change_external_contact.transfer_fail' => TransferFailRawEvent::class,
            'change_external_chat.create' => CreateRoomRawEvent::class,
            'change_external_chat.update' => UpdateRoomRawEvent::class,
            'change_external_chat.dismiss' => DismissRoomRawEvent::class,
            'change_external_tag.create' => CreateTagRawEvent::class,
            'change_external_tag.update' => UpdateTagRawEvent::class,
            'change_external_tag.delete' => DeleteTagRawEvent::class,
            'change_external_tag.shuffle' => ShuffleTagRawEvent::class,
            'change_contact.create_user' => CreateEmployeeRawEvent::class,
            'change_contact.update_user' => UpdateEmployeeRawEvent::class,
            'change_contact.delete_user' => DeleteEmployeeRawEvent::class,
            'change_contact.create_party' => CreateDepartmentRawEvent::class,
            'change_contact.update_party' => UpdateDepartmentRawEvent::class,
            'change_contact.delete_party' => DeleteDepartmentRawEvent::class,
            'change_contact.update_tag' => UpdateContactTagRawEvent::class,
            'conversation_archive' => MessageArchiveRawEvent::class,
            'msgaudit_notify' => MessageNotifyRawEvent::class,
        ];
    }

    /**
     * 获取配置.
     */
    private function getConfig(): array
    {
        $providerConfig = config('framework.wework.provider');
        if (empty($providerConfig) || empty($providerConfig['corp_id'])) {
            throw new CommonException(ErrorCode::INVALID_PARAMS, '企业微信服务商配置为空');
        }

        $appTemplateConfig = config('framework.wework.app_template');

        if (empty($appTemplateConfig) || empty($appTemplateConfig['token'])) {
            throw new CommonException(ErrorCode::INVALID_PARAMS, '企业微信服务商自建应用代开发模板配置为空');
        }

        $config = [
            // 应用代开发模版id即为suite_id
            'suite_id' => $appTemplateConfig['id'],
            'corp_id' => $providerConfig['corp_id'],
            'secret' => $providerConfig['secret'],
            'token' => $appTemplateConfig['token'],
            'aes_key' => $appTemplateConfig['aes_key'],
        ];

        if (! $this->request->has('corpId')) {
            return $config;
        }

        $corpId = (int) $this->request->input('corpId');
        if (empty($corpId)) {
            return $config;
        }

        $corp = $this->corpService->getCorpById($corpId, ['wx_corpid', 'auth_type', 'open_wx_corpid']);
        if (! empty($corp)) {
            if ($corp['authType'] == 4) {
                $config['corp_id'] = $corp['openWxCorpid'];
            } else {
                $config['corp_id'] = $corp['wxCorpid'];
            }
        }

        return $config;
    }

    /**
     * 重置请求
     */
    private function reloadRequest(array $config): void
    {
        if ($this->request->isMethod('POST')) {
            return;
        }

        if (empty($corData)) {
            return;
        }
        $request = Context::get(ServerRequestInterface::class)->withQueryParams(array_merge($this->request->query(), [
            'ToUserName' => $config['corp_id'],
        ]));
        Context::set(ServerRequestInterface::class, $request);
    }

    /**
     * 将三方应用或自建应用代开发消息转换为内部开发模式消息格式.
     *
     * @return array
     */
    private function transformMessage(array $message)
    {
        if (! array_key_exists('ToUserName', $message)) {
            $message['ToUserName'] = $message['AuthCorpId'] ?? '';
            $message['FromUserName'] = 'sys';
            $message['CreateTime'] = $message['TimeStamp'] ?? '';
            $message['event'] = 'event';
            $message['MsgType'] = $message['InfoType'] ?? '';
        }

        return $message;
    }
}
